Skip to content

feat(hooks): add Before/AfterReduceContextEvent for context reduction observability#2227

Open
VincentBai-dotcom wants to merge 1 commit intostrands-agents:mainfrom
VincentBai-dotcom:feat/reduce-context-events
Open

feat(hooks): add Before/AfterReduceContextEvent for context reduction observability#2227
VincentBai-dotcom wants to merge 1 commit intostrands-agents:mainfrom
VincentBai-dotcom:feat/reduce-context-events

Conversation

@VincentBai-dotcom
Copy link
Copy Markdown

@VincentBai-dotcom VincentBai-dotcom commented Apr 29, 2026

Motivation

Observability plugins need to detect when conversation context compaction occurs. The only existing approach was polling conversation_manager.removed_message_count and computing deltas — fragile, timing-dependent, and can miss events or produce duplicates. The SDK emits hook events for model calls, tool calls, and invocations, but had no event for context reduction.

Resolves: #2048

Public API Changes

Two new hook events and a template-method change to ConversationManager:

from strands import Agent
from strands.hooks import HookProvider, HookRegistry, AfterReduceContextEvent

class CompactionObserver(HookProvider):
    def register_hooks(self, registry: HookRegistry) -> None:
        registry.add_callback(AfterReduceContextEvent, self._on_reduced)

    def _on_reduced(self, event: AfterReduceContextEvent) -> None:
        print(f"Removed {event.messages_removed} messages "
              f"({event.message_count_before}{event.message_count_after})")

agent = Agent(hooks=[CompactionObserver()])

ConversationManager.reduce_context() is now a concrete template method that wraps _reduce_context() with event emission. Subclasses should override _reduce_context() instead. Third-party subclasses that still override reduce_context() directly continue to work — an __init_subclass__ shim transparently re-wires them and emits a DeprecationWarning.

Events fire on all code paths: reactive overflow in the event loop, proactive apply_management, per-turn hooks, and direct tool calls.

Breaking Changes

ConversationManager subclasses that override reduce_context() directly will see a DeprecationWarning nudging them to override _reduce_context() instead. Existing behavior is preserved — no code changes required.

Migration

# Before
class MyManager(ConversationManager):
    def reduce_context(self, agent, e=None, **kwargs):
        agent.messages[:] = agent.messages[-10:]

# After
class MyManager(ConversationManager):
    def _reduce_context(self, agent, e=None, **kwargs):
        agent.messages[:] = agent.messages[-10:]

Type of Change

New feature

Testing

  • I ran hatch run prepare

Checklist

  • I have read the CONTRIBUTING document
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly
  • I have added an appropriate example to the documentation to outline the feature, or no new docs are needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

…ager

Move event emission from the single overflow call site in agent.py into
ConversationManager.reduce_context as a concrete template method. Subclasses
implement _reduce_context; the framework wraps it with Before/After event
emission so every call path (reactive overflow, proactive apply_management,
per-turn, direct tool calls) gets events automatically.

An __init_subclass__ shim detects third-party subclasses that override
reduce_context directly, transparently re-wires them to _reduce_context,
and emits a DeprecationWarning.

Closes strands-agents#2048
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Expose ConversationManager's reduce_context() as Hook Event

1 participant